package com.ruoyi.uitity;

import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.framework.config.MinioConfig;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static java.lang.System.in;

@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioConfig configuration;

    /**
     * 判断bucket是否存在，不存在则创建
     */
    public boolean existBucket(String bucketName) {
        boolean exists;
        try {
            exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                exists = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            exists = false;
        }
        return exists;
    }

    /**
     * 删除bucket
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 上传文件
     *
     * @param file     文件
     * @param fileName 文件名称
     */
    public void upload(MultipartFile file, String fileName) {
        // 使用putObject上传一个文件到存储桶中。
        try {
            InputStream inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取文件访问地址（有过期时间）
     *
     * @param fileName 文件名称
     * @param time     时间
     * @param timeUnit 时间单位
     */
    public String getExpireFileUrl(String fileName, int time, TimeUnit timeUnit) {
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .expiry(time, timeUnit).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取文件访问地址
     *
     * @param fileName 文件名称
     */
    public String getFileUrl(String fileName) {
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 下载文件
     *
     * @param fileName 文件名称
     */
    public void download(HttpServletResponse response, String fileName) {
        InputStream in = null;
        try {
            // 获取对象信息
            StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(configuration.getBucketName()).object(fileName).build());
            response.setContentType(stat.contentType());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            // 文件下载
            in = minioClient.getObject(GetObjectArgs.builder().bucket(configuration.getBucketName()).object(fileName).build());
            IOUtils.copy(in, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 删除文件
     *
     * @param fileName 文件名称
     */
    public void delete(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(configuration.getBucketName()).object(fileName).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void downloadAsZip(HttpServletResponse response, List<String> fileNames) throws IOException {
        String zipFileName = "download_" + System.currentTimeMillis() + ".zip";
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, String.valueOf(StandardCharsets.UTF_8)));

        try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
            for (String fileName : fileNames) {
                try (InputStream in = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(configuration.getBucketName())
                        .object(fileName)
                        .build())) {
                    zipOutputStream.putNextEntry(new ZipEntry(fileName));
                    IOUtils.copy(in, zipOutputStream);
                    zipOutputStream.closeEntry();
                } catch (IOException | ErrorResponseException | InsufficientDataException | InternalException |
                         InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException | ServerException |
                         XmlParserException e) {
                    // 可以选择记录日志或者处理异常
                    e.printStackTrace();
                }
            }
            zipOutputStream.finish();
        } catch (IOException e) {
            throw e;
        }
    }

    public String uploadBigFile(MultipartFile file, String fileName) {
        try {
            long startTime = System.currentTimeMillis() / 1000;

            // 获取文件流
            InputStream inputStream = file.getInputStream();
            // 获取文件大小
            long fileSize = file.getSize();
            // 分片大小
            int partSize = 5 * 1024 * 1024; // 5MB
            // 计算分片数量
            int partCount = (int) (fileSize / partSize);
            if (fileSize % partSize > 0) {
                partCount++;
            }
            long partTime = System.currentTimeMillis() / 1000;
            System.out.println("分片耗时" + (partTime - startTime));

            // 存放分片流
            List<InputStream> parts = new ArrayList<>();
            // 存放分片minio地址
            List<String> fileList = new ArrayList<>();

            // 分配分片流
            for (int i = 0; i < partCount; i++) {
                byte[] partData = new byte[partSize];
                int read = inputStream.read(partData);
                if (read == -1) {
                    break; // 文件已经读完了
                }
                // 将读取的数据作为一个新的InputStream添加到parts列表中
                parts.add(new ByteArrayInputStream(partData, 0, read));
            }
            long readTime = System.currentTimeMillis() / 1000;
            System.out.println("读取文件耗时" + (readTime - partTime));

            // 上传分片流到minio
            for (int i = 0; i < parts.size(); i++) {
                String partObjectName = fileName + ".part" + i;
                fileList.add(partObjectName);
                InputStream partStream = parts.get(i);
                PutObjectArgs args = PutObjectArgs.builder()
                        .bucket(configuration.getBucketName())
                        .object(partObjectName)
                        .stream(partStream, partStream.available(), -1)
                        .contentType(file.getContentType())
                        .build();
                minioClient.putObject(args);
            }
            long upLoadTime = System.currentTimeMillis() / 1000;
            System.out.println("上传分片耗时" + (upLoadTime - readTime));

            // 关闭主文件输入流和分片输入流
            inputStream.close();
            for (InputStream part : parts) {
                part.close();
            }

            // 获取需要合并的分片组装成ComposeSource
            List<ComposeSource> sourceObjectList = new ArrayList<>(fileList.size());
            for (String chunk : fileList) {
                sourceObjectList.add(
                        ComposeSource.builder()
                                .bucket(configuration.getBucketName())
                                .object(chunk)
                                .build()
                );
            }

            // 合并分片
            ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .sources(sourceObjectList)
                    .build();
            minioClient.composeObject(composeObjectArgs);
            long mergeTime = System.currentTimeMillis() / 1000;
            System.out.println("合并分片耗时" + (mergeTime - upLoadTime));

            // 删除已经上传的分片
            List<DeleteObject> deleteObjects = fileList.stream().map(DeleteObject::new).collect(Collectors.toList());
            RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder()
                    .bucket(configuration.getBucketName())
                    .objects(deleteObjects)
                    .build();
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
            try {
                for (Result<DeleteError> result : results) {
                    DeleteError deleteError = result.get();
                    System.out.println("error in deleting object" + deleteError.objectName() + ";" + deleteError.message());
                }
            } catch (Exception e) {
                System.out.println("minio删除文件失败");
                e.printStackTrace();
            }
            long deleteTime = System.currentTimeMillis() / 1000;
            System.out.println("删除分片耗时" + (deleteTime - mergeTime));

            return fileName;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public void downloadBigAsZip(HttpServletResponse response, List<String> fileNames) throws IOException {
        String zipFileName = "download_" + System.currentTimeMillis() + ".zip";
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, String.valueOf(StandardCharsets.UTF_8)));

        try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
            // 分片大小设置为5MB
            int bufferSize = 1 * 1024 * 1024; // 5MB
            byte[] buffer = new byte[bufferSize];

            for (String fileName : fileNames) {
                try (InputStream in = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(configuration.getBucketName())
                        .object(fileName)
                        .build())) {

                    zipOutputStream.putNextEntry(new ZipEntry(fileName));

                    int bytesRead;
                    while ((bytesRead = in.read(buffer)) != -1) {
                        zipOutputStream.write(buffer, 0, bytesRead);
                        // 刷新输出流，确保数据及时发送
                        zipOutputStream.flush();
                    }

                    zipOutputStream.closeEntry();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            zipOutputStream.finish();
        } catch (IOException e) {
            throw e;
        }
    }
}
